package jxtend.processor.eclipse;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import jxtend.processor.exception.JxtendProcessorException;
import jxtend.processor.reflect.ReflectField;
import jxtend.processor.utils.ReflectUtils;

import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.ClassFilePool;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.util.CompoundNameVector;
import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;
import org.eclipse.jdt.internal.compiler.util.HashtableOfPackage;
import org.eclipse.jdt.internal.compiler.util.HashtableOfType;
import org.eclipse.jdt.internal.compiler.util.ObjectVector;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
import org.eclipse.jdt.internal.compiler.util.SimpleNameVector;

@SuppressWarnings("restriction")
/* default */ final class EcjCollectionUtils {

	private static final ReflectField<CompoundNameVector, Integer> maxSizeOfCompoundNameVector =
		ReflectField.getField(CompoundNameVector.class, int.class, "maxSize");
	private static final ReflectField<CompoundNameVector, char[][][]> elementsOfCompoundNameVector =
		ReflectField.getField(CompoundNameVector.class, char[][][].class, "elements");

	private static final ReflectField<SimpleNameVector, Integer> maxSizeOfSimpleNameVector =
		ReflectField.getField(SimpleNameVector.class, int.class, "maxSize");
	private static final ReflectField<SimpleNameVector, char[][]> elementsOfSimpleNameVector =
		ReflectField.getField(SimpleNameVector.class, char[][].class, "elements");

	private static final ReflectField<ObjectVector, Integer> maxSizeOfObjectVector =
		ReflectField.getField(ObjectVector.class, int.class, "maxSize");
	private static final ReflectField<ObjectVector, Object[]> elementsOfObjectVector =
		ReflectField.getField(ObjectVector.class, Object[].class, "elements");
	
	private static final ReflectField<HashtableOfType, Integer> thresholdOfHashtableOfType =
		ReflectField.getField(HashtableOfType.class, int.class, "threshold");

	private static final ReflectField<HashtableOfPackage, Integer> thresholdOfHashtableOfPackage =
		ReflectField.getField(HashtableOfPackage.class, int.class, "threshold");
	
	private static final Constructor<ClassFilePool> constructorOfClassFilePool =
		ReflectUtils.getInaccessibleConstructor(ClassFilePool.class);

	private static final ReflectField<ClassFilePool, ClassFile[]> classFilesOfClassFilePool =
		ReflectField.getField(ClassFilePool.class, ClassFile[].class, "classFiles");


	public static HashtableOfObject clone(HashtableOfObject source) {
		if (source == null) {
			return null;
		}

		try {
			return (HashtableOfObject) source.clone();
		} catch (CloneNotSupportedException e) {
			throw new JxtendProcessorException(e);
		}
	}

	public static CompoundNameVector clone(CompoundNameVector source) {
		if (source == null) {
			return null;
		}

		CompoundNameVector result = new CompoundNameVector();
		if (source.size <= maxSizeOfCompoundNameVector.get(result)) {
			System.arraycopy(elementsOfCompoundNameVector.get(source), 0,
					elementsOfCompoundNameVector.get(result), 0, source.size);
			result.size = source.size;
		} else {
			elementsOfCompoundNameVector.set(result, new char[elementsOfCompoundNameVector.get(source).length][][]);
			result.size = source.size;
			maxSizeOfCompoundNameVector.set(result, maxSizeOfCompoundNameVector.get(source));
		}

		return result;
	}

	public static SimpleNameVector clone(SimpleNameVector source) {
		if (source == null) {
			return null;
		}

		SimpleNameVector result = new SimpleNameVector();
		if (source.size <= maxSizeOfSimpleNameVector.get(result)) {
			System.arraycopy(elementsOfSimpleNameVector.get(source), 0,
					elementsOfSimpleNameVector.get(result), 0, source.size);
			result.size = source.size;
		} else {
			elementsOfSimpleNameVector.set(result, new char[elementsOfSimpleNameVector.get(source).length][]);
			result.size = source.size;
			maxSizeOfSimpleNameVector.set(result, maxSizeOfSimpleNameVector.get(source));
		}

		return result;
	}

	public static ObjectVector clone(ObjectVector source) {
		if (source == null) {
			return null;
		}

		ObjectVector result = new ObjectVector();
		if (source.size <= maxSizeOfObjectVector.get(result)) {
			System.arraycopy(elementsOfObjectVector.get(source), 0,
					elementsOfObjectVector.get(result), 0, source.size);
			result.size = source.size;
		} else {
			elementsOfObjectVector.set(result, new Object[elementsOfObjectVector.get(source).length]);
			result.size = source.size;
			maxSizeOfObjectVector.set(result, maxSizeOfObjectVector.get(source));
		}

		return result;
	}	

	public static HashtableOfType clone(HashtableOfType source) {
		if (source == null) {
			return null;
		}

		HashtableOfType result = new HashtableOfType();
		result.elementSize = source.elementSize;

		thresholdOfHashtableOfType.set(result, thresholdOfHashtableOfType.get(source));

		result.keyTable = new char[source.keyTable.length][];
		System.arraycopy(source.keyTable, 0, result.keyTable, 0, source.keyTable.length);

		result.valueTable = new ReferenceBinding[source.valueTable.length];
		System.arraycopy(source.valueTable, 0, result.valueTable, 0, source.valueTable.length);

		return result;
	}

	public static HashtableOfPackage clone(HashtableOfPackage source) {
		if (source == null) {
			return null;
		}

		HashtableOfPackage result = new HashtableOfPackage();
		result.elementSize = source.elementSize;

		thresholdOfHashtableOfPackage.set(result, thresholdOfHashtableOfPackage.get(source));

		result.keyTable = new char[source.keyTable.length][];
		System.arraycopy(source.keyTable, 0, result.keyTable, 0, source.keyTable.length);

		result.valueTable = new PackageBinding[source.valueTable.length];
		System.arraycopy(source.valueTable, 0, result.valueTable, 0, source.valueTable.length);

		return result;
	}

	public static ClassFilePool clone(ClassFilePool source) {
		if (source == null) {
			return null;
		}

		try {
			ClassFilePool result = constructorOfClassFilePool.newInstance();
			classFilesOfClassFilePool.set(result, classFilesOfClassFilePool.get(source));

			return result;
		} catch (IllegalAccessException e) {
			throw new JxtendProcessorException(e);
		} catch (IllegalArgumentException e) {
			throw new JxtendProcessorException(e);
		} catch (InstantiationException e) {
			throw new JxtendProcessorException(e);
		} catch (InvocationTargetException e) {
			throw new JxtendProcessorException(e);
		}		
	}

	public static SimpleLookupTable clone(SimpleLookupTable source) {
		if (source == null) {
			return null;
		}

		SimpleLookupTable result = new SimpleLookupTable();
		result.elementSize = source.elementSize;
		result.threshold = source.threshold;
		
		result.keyTable = new Object[source.keyTable.length];
		System.arraycopy(source.keyTable, 0, result.keyTable, 0, source.keyTable.length);

		result.valueTable = new Object[source.valueTable.length];
		System.arraycopy(source.valueTable, 0, result.valueTable, 0, source.valueTable.length);

		return result;
	}	
	
	private EcjCollectionUtils() {
		// Utility class
	}
}
